/**
 * @class 精确恒星实体（星座恒星，系内近距，精确参数）
 * @author DuskyHuang 圣子
*/

import { _decorator, Component, Node, Vec3, MeshRenderer, Color, Camera } from 'cc';
import { core, Notifications, utils } from 'cfw';
import { ConstGroup, SEC_PER_MIN, SEC_PER_HOUR, DEG_PER_HOUR, StarSpectrum, StarNameType, SceneGraph, StarLevel } from 'const';
import { polar2cartesian, absoluteToRadius, equator2ecliptic, pyramidCulling, attenuate } from 'algorithms';
import { setColorBySpectrum, setLabelStyle } from 'helpers';
import { constStars, cosmos, ui, UIProxy, visit } from 'proxies';
import { CelestialLabel } from '../ui/items/celestial-label';
import { CelestialNameType } from '../ui/label-i18n-adv';
const { ccclass } = _decorator;

@ccclass('cocosmos.StarPrecise')
export class StarPrecise extends Component implements ICulling {

    public get spectrum(): StarSpectrum { return this._spectrum; }
    public get config(): cstStarConfig { return this._config; }
    public get important(): boolean { return this._important; }
    public get belong(): constellationConfig { return this._belongConfig; }
    public get culling(): boolean { return this._culling; }
    public get placeholder(): boolean { return this._placeholder; }
    public get routing(): boolean {
        const { from, to } = visit.transInfo.stellar?.refers ?? {};
        return this.node === from || this.node === to;
    }

    public loadConfigLine(args: starLine, config: constellationConfig): true | void {
        this._belongConfig = config;
        const { cst, group, gd } = this._belongConfig;
        this._group = group;
        const chars = args.split(',');
        const [ab, cname, fid, hd, hip, ra_hms, dec_hms, mag, abs, dist, spec, bias] = chars;
        this._placeholder = ab.startsWith('&');
        this._ab = ab;
        this._config = { ab, cname, fid, HD: hd ? +hd : null, HIP: hip ? +hip : null, mag: +mag, abs: +abs, dist: +dist, spec, bias } as cstStarConfig;
        this._config.ra_hms = ra_hms as hms;
        this._config.dec_hms = dec_hms as hms;
        const [H, M, S] = ra_hms.split(':');
        this._config.RA = ((+H) + (+M) / SEC_PER_MIN + (+S) / SEC_PER_HOUR) * DEG_PER_HOUR;
        const [h, m, s] = dec_hms.split(':');
        this._config.Dec = ((+h) + (+m) / SEC_PER_MIN + (+s) / SEC_PER_HOUR);
        this.node.name = `${cst}-${ab}`;
        if (gd && isNum(+ab)) this.node.name += ' G.';
        return this._initAccurate();
    }

    protected onLoad(): void {
        this._mesh = this.getComponent(MeshRenderer);
        this._viewer = utils.findNode(MAIN, COSMOS);
        core.on(Notifications.Time.TICK, this.onTick, this);
    }

    protected onDestroy(): void { core.unload(this); }

    protected onTick(dt?: time.sec, tick?: num.int): void {
        if (this._placeholder) return;
        this._syncActivating();
        this._syncInstancing();
        this._syncNameLabel();
    }

    private _syncActivating(): void {
        this._culling = false;
        if (!this.node.parent || !this.node.parent.activeInHierarchy) return;
        if (this.routing) {
            this.node.active = true;
            return;
        }
        const activated = (Vec3.squaredDistance(this.node.worldPosition, Vec3.ZERO) > WPS_VLV_SQR)
            && (this.important || this._config.mag <= constStars.showStarLevel);
        if (activated) {
            this._culling = pyramidCulling(this._viewer.getComponent(Camera), this.node, FOV_CULLING_VALVE);
            this.node.active = !this._culling;
        } else this.node.active = false;
    }

    private _syncInstancing(): void {
        this._cArr[3] = this.routing ? 0 : 1;
        this.node.activeInHierarchy && this._mesh.setInstancedAttribute('instanced_color', this._cArr);
    }

    private _syncNameLabel(): void {
        const { starNameLevel: lvl } = ui;
        const premise = lvl !== StarLevel.NONE && this.node.activeInHierarchy;
        const bingo = premise && (this._important || this._config.mag <= lvl);
        const show = bingo && this.node.activeInHierarchy;
        if (!show && this._label) {
            this._label.free();
            this._label = null;
        } else if (show) {
            if (!this._label) {
                const north = this.node.worldPosition.y > 0;
                const label = CelestialLabel.claim('stars', this.node.name);
                label.camera = utils.findCamera(COSMOS, DEEP);
                label.target = this.node;
                this._label = setLabelStyle(label, 'stars', north);
                const { bias, cname, HD, HIP } = this._config;
                this._label.setMap({
                    [StarNameType.ALPHABET]: { en: this.node.name, cn: this._belongConfig.cname + '-' + this._ab },
                    [StarNameType.NAMED]: { en: bias, cn: cname },
                    [StarNameType.HD]: HD ? ('HD-' + HD) : '',
                    [StarNameType.HIP]: HIP ? ('HIP-' + HIP) : ''
                }, CelestialNameType.Star);
            }
            this._attenuateLabel();
        }
    }

    private _attenuateLabel(): void {
        if (!this._label) return;
        const north = this.node.worldPosition.y > 0;
        if (this.routing) {
            this._label.setOffset(0, 0, 0);
            this._label.setUIOffset(0, north ? 30 : -30);
            this._label.alpha = Math.clamp01(cosmos.pow10 + 1);
            return;
        } else {
            this._label.setOffset(0, north ? 0.6 : -0.6, 0);
        }
        const lenSqr = Vec3.squaredDistance(this.node.worldPosition, Vec3.ZERO);
        this._label.alpha = attenuate(lenSqr, NameLabelAtt.heads(2) as vec2, true);
    }

    private _initAccurate(): true | void {
        const { ab, RA, Dec, dist, abs } = this._config;
        if (isNaN(RA)) return console.warn(`[${this.node.name}]:ra`, RA);
        if (isNaN(Dec)) return console.warn(`[${this.node.name}]:dec`, Dec);
        if (isNaN(dist)) return console.warn(`[${this.node.name}]:dist`, dist);
        polar2cartesian(RA, Dec, dist, v3a);
        if (isNaN(v3a.x) || isNaN(v3a.y) || isNaN(v3a.z)) return console.warn(`[${this.node.name}]:pos`, v3a);
        if (dist < 1) return console.warn(`[${this.node.name}]:too-near,dist=`, dist);
        equator2ecliptic(v3a, v3a);
        this.node.setPosition(v3a);
        const radius = absoluteToRadius(abs);
        this.node.setScale(Vec3.copy(v3a, Vec3.ONE).multiplyScalar(radius));
        if (this._placeholder) {
            this.node.active = false;
            return true;
        }
        this._important = ab.startsWith('α') || (this._group === ConstGroup.ZODIAC &&
            (ab.startsWith('β') || ab.startsWith('γ') || ab.startsWith('δ')));
        this._spectrum = setColorBySpectrum(this._color, this._config.spec, this.node.name);
        const { r, g, b, a } = this._color;
        this._cArr = [r, g, b, a];
        return true;
    }

    private _label: CelestialLabel = null;
    private _viewer: Node;
    private _config: cstStarConfig;
    private _belongConfig: constellationConfig;
    private _group: ConstGroup;
    private _important: boolean;
    private _mesh: MeshRenderer;
    private _spectrum: StarSpectrum;
    private _culling: boolean;
    private _color: Color = new Color;
    private _cArr: number[];
    private _ab: string;
    private _placeholder: boolean = false;
}

const { COSMOS, Cameras: { DEEP, MAIN } } = SceneGraph;
const { NameLabelAtt, WPS_CULLING_VALVE, FOV_CULLING_VALVE } = UIProxy;
const WPS_VLV_SQR = WPS_CULLING_VALVE * WPS_CULLING_VALVE;
const v3a = new Vec3;